Notebook for cell type identity correlations
General Setup
Setup chunk
Setup reticulate
knitr::opts_chunk$set(fig.width = 8)
knitr::opts_knit$set(root.dir = normalizePath(".."))
knitr::opts_knit$get("root.dir")
[1] "/nas/groups/treutlein/USERS/tomasgomes/projects/pallium_evo"
Load libraries
library(reticulate)
knitr::knit_engines$set(python = reticulate::eng_python)
py_available(initialize = FALSE)
[1] TRUE
use_python(Sys.which("python"))
py_config()
python: /home/tpires/bin/miniconda3/bin/python
libpython: /home/tpires/bin/miniconda3/lib/libpython3.8.so
pythonhome: /home/tpires/bin/miniconda3:/home/tpires/bin/miniconda3
version: 3.8.3 (default, May 19 2020, 18:47:26) [GCC 7.3.0]
numpy: /home/tpires/bin/miniconda3/lib/python3.8/site-packages/numpy
numpy_version: 1.18.5
NOTE: Python version was forced by RETICULATE_PYTHON
Load and reformat data
Load Seurat data
library(Seurat)
library(dplyr)
library(tidyverse)
library(igraph)
library(UpSetR)
Load axolotl data
lizard_all = readRDS("data/expression/lizard_all_v3.RDS")
turtle_all = readRDS("data/expression/turtle_all_v3.RDS")
drerio_brain = readRDS("data/expression/drerio_brain_v3.RDS")
finches_brain = readRDS("data/expression/HVC_RA_X.RDS")
finches_list = SplitObject(finches_brain, split.by = "species")
mouse_brain = readRDS("data/expression/l5_all_seurat.RDS")
Merge axolotl annotations together into a single object
axolotl_gaba = readRDS(file = "data/expression/axolotl/pallium_neuronal_GABA_res07_harmony.RDS")
axolotl_glut = readRDS(file = "data/expression/axolotl/pallium_neuronal_Glut_res05_harmony.RDS")
axolotl_neurons = readRDS(file = "data/expression/axolotl/pallium_neuronal_res07_harmony.RDS")
axolotl_all = readRDS(file = "data/expression/axolotl/palliumres07_harmony.RDS")
Organise metadata into a list
gaba_cl = paste0("GABA_", axolotl_gaba$seurat_clusters)
names(gaba_cl) = colnames(axolotl_gaba)
glut_cl = paste0("Glut_", axolotl_glut$seurat_clusters)
names(glut_cl) = colnames(axolotl_glut)
other = axolotl_all$classification_2[!(colnames(axolotl_all) %in% c(names(glut_cl), names(gaba_cl)))]
all_annot = data.frame("all_annot" = c(other, gaba_cl, glut_cl))
axolotl_all = AddMetaData(axolotl_all, metadata = all_annot)
axolotl_all_filt = axolotl_all[,!is.na(axolotl_all$all_annot) & !grepl("neuron", axolotl_all$all_annot)]
Load necessary eggNOG data
meta_list = list("exp_tur" = turtle_all@meta.data, "exp_liz" = lizard_all@meta.data,
"exp_dre" = drerio_brain@meta.data, "exp_zef" = finches_list$ZF@meta.data,
"exp_bef" = finches_list$BF@meta.data, "exp_mou" = mouse_brain@meta.data,
"exp_axo" = axolotl_all_filt@meta.data)
cl_id_list = list("cluster", "clusters", "Mod2.5", "seurat_clusters", "seurat_clusters", "Taxonomy_group",
"all_annot")
names(cl_id_list) = names(meta_list)
Prepare datasets
Reformat matrices to have only one2one orthologs that are shared
annot_list = readRDS(file = "data/eggNOG/annotation_list.RDS")
Renormalise, get mean expression and markers
counts_list = list(exp_tur = turtle_all@assays$RNA@counts, exp_liz = lizard_all@assays$RNA@counts,
exp_dre = drerio_brain@assays$RNA@counts, exp_zef = finches_list$ZF@assays$RNA@counts,
exp_bef = finches_list$BF@assays$RNA@counts, exp_mou = mouse_brain@assays$RNA@counts,
exp_axo = axolotl_all_filt@assays$RNA@counts)
reformatWithOrth = function(eggnog_annot, counts_l, sp_list = c("Cpicta", "Pvitticeps")){
l_g_sp = list()
for(spi in 1:length(sp_list)){
cp = names(counts_l)[spi]
sp = sp_list[spi]
l_g_sp[[cp]] = eggnog_annot[[sp]][,c(2,5)]
l_g_sp[[cp]] = l_g_sp[[cp]][l_g_sp[[cp]]$Preferred_name!="-" & !is.na(l_g_sp[[cp]]$Preferred_name),]
l_g_sp[[cp]]$Preferred_name = toupper(l_g_sp[[cp]]$Preferred_name)
l_g_sp[[cp]] = unique(l_g_sp[[cp]])
l_g_sp[[cp]] = l_g_sp[[cp]][!(l_g_sp[[cp]]$gene %in% l_g_sp[[sp]]$gene[duplicated(l_g_sp[[cp]]$gene)]),]
l_g_sp[[cp]] = l_g_sp[[cp]][!(l_g_sp[[cp]]$Preferred_name %in% l_g_sp[[cp]]$Preferred_name[duplicated(l_g_sp[[cp]]$Preferred_name)]),]
}
all_sp_match = l_g_sp %>% reduce(full_join, by = "Preferred_name")
all_sp_match = all_sp_match[,c(2,1,3:ncol(all_sp_match))]
colnames(all_sp_match) = c("Preferred_name", names(counts_l))
# only keep genes appearing in exp matrix
g_in = lapply(colnames(all_sp_match)[-1],
function(x) toupper(all_sp_match[,x]) %in% toupper(rownames(counts_l[[x]])))
keep = rep(T, nrow(all_sp_match))
for(n in 1:length(g_in)){
keep = keep & g_in[[n]]
}
all_sp_match_filt = all_sp_match[keep,]
# remove duplicated - only want one2one
g_du = lapply(colnames(all_sp_match_filt)[-1],
function(x) !(all_sp_match_filt[,x] %in% all_sp_match_filt[,x][duplicated(all_sp_match_filt[,x])]))
keep = rep(T, nrow(all_sp_match_filt))
for(n in 1:length(g_du)){
keep = keep & g_du[[n]]
}
all_sp_match_filt = all_sp_match_filt[keep,]
# putting everything into uppercase because the drerio data is in uppercase for some reason...
for(n in colnames(all_sp_match_filt)){all_sp_match_filt[,n] = toupper(all_sp_match_filt[,n])}
return(all_sp_match_filt)
}
gene_match = reformatWithOrth(annot_list, counts_list,
sp_list = c("Cpicta", "Pvitticeps", "Drerio", "Tguttata",
"Tguttata", "Mmusculus", "Amexicanum"))
gene_match_comp = gene_match[complete.cases(gene_match),]
lapply(counts_list, dim)
$exp_tur
[1] 23500 18828
$exp_liz
[1] 15345 4187
$exp_dre
[1] 26586 58492
$exp_zef
[1] 19857 39681
$exp_bef
[1] 19857 4105
$exp_mou
[1] 27998 160796
$exp_axo
[1] 82960 15269
for(n in names(counts_list)){
counts_list[[n]] = counts_list[[n]][toupper(rownames(counts_list[[n]])) %in% gene_match_comp[,n],]
rownames(counts_list[[n]]) = gene_match_comp$Preferred_name[match(toupper(rownames(counts_list[[n]])),
gene_match_comp[,n])]
}
lapply(counts_list, dim)
$exp_tur
[1] 4805 18828
$exp_liz
[1] 4805 4187
$exp_dre
[1] 4833 58492
$exp_zef
[1] 4805 39681
$exp_bef
[1] 4805 4105
$exp_mou
[1] 4805 160796
$exp_axo
[1] 4805 15269
# make sure they have the same ordering
counts_list = lapply(counts_list, function(x) x[rownames(counts_list$exp_axo),])
Correlation analysis
Load data
load("results/cross_species_correlation/one2one_seurat_avg_mk.RData")
Select genes to use for correlations, normalise data
load("results/cross_species_correlation/one2one_seurat_avg_mk.RData")
upset(fromList(top_mk_list[c(6,4,5,2,1,7,3)]), sets = names(top_mk_list)[c(6,4,5,2,1,7,3)],
order.by = "freq", keep.order = TRUE, number.angles = 0, nintersects = 30, point.size = 1.8)
Calculate correlations
Plot correlation matrices
sp_cor_l = list()
for(i in names(avg_exp_sub)){
for(j in names(avg_exp_sub)){
print(paste(i, j))
sp_cor_l[[paste0(i, ".", j)]] = cor(avg_exp_sub[[i]], avg_exp_sub[[j]], method = "sp")
}
}
[1] "exp_tur exp_tur"
[1] "exp_tur exp_liz"
[1] "exp_tur exp_dre"
[1] "exp_tur exp_zef"
[1] "exp_tur exp_bef"
[1] "exp_tur exp_mou"
[1] "exp_tur exp_axo"
[1] "exp_liz exp_tur"
[1] "exp_liz exp_liz"
[1] "exp_liz exp_dre"
[1] "exp_liz exp_zef"
[1] "exp_liz exp_bef"
[1] "exp_liz exp_mou"
[1] "exp_liz exp_axo"
[1] "exp_dre exp_tur"
[1] "exp_dre exp_liz"
[1] "exp_dre exp_dre"
[1] "exp_dre exp_zef"
[1] "exp_dre exp_bef"
[1] "exp_dre exp_mou"
[1] "exp_dre exp_axo"
[1] "exp_zef exp_tur"
[1] "exp_zef exp_liz"
[1] "exp_zef exp_dre"
[1] "exp_zef exp_zef"
[1] "exp_zef exp_bef"
[1] "exp_zef exp_mou"
[1] "exp_zef exp_axo"
[1] "exp_bef exp_tur"
[1] "exp_bef exp_liz"
[1] "exp_bef exp_dre"
[1] "exp_bef exp_zef"
[1] "exp_bef exp_bef"
[1] "exp_bef exp_mou"
[1] "exp_bef exp_axo"
[1] "exp_mou exp_tur"
[1] "exp_mou exp_liz"
[1] "exp_mou exp_dre"
[1] "exp_mou exp_zef"
[1] "exp_mou exp_bef"
[1] "exp_mou exp_mou"
[1] "exp_mou exp_axo"
[1] "exp_axo exp_tur"
[1] "exp_axo exp_liz"
[1] "exp_axo exp_dre"
[1] "exp_axo exp_zef"
[1] "exp_axo exp_bef"
[1] "exp_axo exp_mou"
[1] "exp_axo exp_axo"
UMAP from mean expression
cols = colorRampPalette(c(rev(RColorBrewer::brewer.pal(9, "Blues")),
RColorBrewer::brewer.pal(9, "Reds")))(100)
for(n in names(sp_cor_l)){
pltmat = t(sp_cor_l[[n]])
br = c(seq(min(pltmat), 0, length.out = 51), seq(0, max(pltmat), length.out = 51)[-1])
pheatmap::pheatmap(pltmat, breaks = br, color = cols,
clustering_method = "ward.D2", main = n)
}

















































Network in MDS from correlations
cor_tab_l = list()
for(n in names(sp_cor_l)){
dat = reshape2::melt(sp_cor_l[[n]])
dat$sp1 = strsplit(strsplit(n, ".", fixed = T)[[1]][1], "_", fixed = T)[[1]][2]
dat$sp2 = strsplit(strsplit(n, ".", fixed = T)[[1]][2], "_", fixed = T)[[1]][2]
dat$Var1 = paste0(dat$sp1, "_", dat$Var1)
dat$Var2 = paste0(dat$sp2, "_", dat$Var2)
cor_tab_l[[n]] = dat
}
cor_tab = Reduce(rbind, cor_tab_l)
cor_sp = reshape2::dcast(cor_tab, Var1 ~ Var2, value.var = "value")
rownames(cor_sp) = cor_sp[,1]
cor_sp = cor_sp[,-1]
adj_mat = cor_sp>=0.3
# build graph, project with MDS
network_sp = graph_from_adjacency_matrix(adj_mat, weighted=T, mode="undirected", diag=F)
l_sp = igraph::layout_with_mds(network_sp)
l_sp = data.frame(l_sp)
l_sp$cl = colnames(adj_mat)
rownames(l_sp) = colnames(adj_mat)
l_sp$sp = unlist(lapply(strsplit(l_sp$cl, "_"), function(x) x[1]))
l_sp$cl = unlist(lapply(strsplit(l_sp$cl, "_"), function(x) x[2]))
df_names = l_sp[l_sp$sp=="axo",]
ggplot(l_sp, aes(x = X1, y = X2, colour = sp))+
geom_point()+
ggrepel::geom_text_repel(data = df_names, mapping = aes(x = X1, y = X2, label = cl), max.overlaps = 100)+
theme_classic()
common_genes = Reduce(intersect, top_mk_list)
any_genes = Reduce(union, top_mk_list)
non_shared_genes = any_genes[!(any_genes %in% common_genes)]
non_shared_genes = non_shared_genes[non_shared_genes %in% Reduce(intersect, lapply(avg_exp, rownames))]
avg_exp_sub = lapply(avg_exp, function(x) t(apply(x[non_shared_genes,], 1, function(y) y/mean(y))))
lapply(avg_exp_sub, dim)
for(n in names(avg_exp_sub)){
sp = strsplit(n, "_")[[1]][2]
colnames(avg_exp_sub[[n]]) = paste0(sp, "..", colnames(avg_exp_sub[[n]]))
}
avg_exp_all = t(Reduce(cbind, avg_exp_sub))
set.seed(2954)
l = uwot::umap(avg_exp_all, metric = "cosine", ret_nn = T, n_epochs = 1000)
l_sp = data.frame(l$embedding)
rownames(l_sp) = rownames(avg_exp_all)
l_sp$cl = rownames(avg_exp_all)
l_sp$sp = unlist(lapply(strsplit(l_sp$cl, "..", fixed = T), function(x) x[1]))
l_sp$cl = unlist(lapply(strsplit(l_sp$cl, "..", fixed = T), function(x) x[2]))
df_names = l_sp[l_sp$sp=="axo",]
ggplot(l_sp, aes(x = X1, y = X2, colour = sp))+
geom_point()+
ggrepel::geom_text_repel(data = df_names, mapping = aes(x = X1, y = X2, label = cl),
max.overlaps = 100, size = 3)+
theme_classic()
LS0tCnRpdGxlOiAiQ3Jvc3Mgc3BlY2llcyBjb3JyZWxhdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKTm90ZWJvb2sgZm9yIGNlbGwgdHlwZSBpZGVudGl0eSBjb3JyZWxhdGlvbnMKCgoKIyBHZW5lcmFsIFNldHVwClNldHVwIGNodW5rCgpgYGB7ciwgc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcud2lkdGggPSA4KQprbml0cjo6b3B0c19rbml0JHNldChyb290LmRpciA9IG5vcm1hbGl6ZVBhdGgoIi4uIikpCmtuaXRyOjpvcHRzX2tuaXQkZ2V0KCJyb290LmRpciIpCmBgYAoKU2V0dXAgcmV0aWN1bGF0ZQoKYGBge3J9CmxpYnJhcnkocmV0aWN1bGF0ZSkKa25pdHI6OmtuaXRfZW5naW5lcyRzZXQocHl0aG9uID0gcmV0aWN1bGF0ZTo6ZW5nX3B5dGhvbikKcHlfYXZhaWxhYmxlKGluaXRpYWxpemUgPSBGQUxTRSkKdXNlX3B5dGhvbihTeXMud2hpY2goInB5dGhvbiIpKQpweV9jb25maWcoKQpgYGAKCkxvYWQgbGlicmFyaWVzCgpgYGB7cn0KbGlicmFyeShTZXVyYXQpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGlncmFwaCkKbGlicmFyeShVcFNldFIpCmBgYAoKCgojIExvYWQgYW5kIHJlZm9ybWF0IGRhdGEKTG9hZCBTZXVyYXQgZGF0YQoKYGBge3J9CmxpemFyZF9hbGwgPSByZWFkUkRTKCJkYXRhL2V4cHJlc3Npb24vbGl6YXJkX2FsbF92My5SRFMiKQp0dXJ0bGVfYWxsID0gcmVhZFJEUygiZGF0YS9leHByZXNzaW9uL3R1cnRsZV9hbGxfdjMuUkRTIikKZHJlcmlvX2JyYWluID0gcmVhZFJEUygiZGF0YS9leHByZXNzaW9uL2RyZXJpb19icmFpbl92My5SRFMiKQpmaW5jaGVzX2JyYWluID0gcmVhZFJEUygiZGF0YS9leHByZXNzaW9uL0hWQ19SQV9YLlJEUyIpCmZpbmNoZXNfbGlzdCA9IFNwbGl0T2JqZWN0KGZpbmNoZXNfYnJhaW4sIHNwbGl0LmJ5ID0gInNwZWNpZXMiKQptb3VzZV9icmFpbiA9IHJlYWRSRFMoImRhdGEvZXhwcmVzc2lvbi9sNV9hbGxfc2V1cmF0LlJEUyIpCmBgYAoKTG9hZCBheG9sb3RsIGRhdGEKCmBgYHtyfQpheG9sb3RsX2dhYmEgPSByZWFkUkRTKGZpbGUgPSAiZGF0YS9leHByZXNzaW9uL2F4b2xvdGwvcGFsbGl1bV9uZXVyb25hbF9HQUJBX3JlczA3X2hhcm1vbnkuUkRTIikKYXhvbG90bF9nbHV0ID0gcmVhZFJEUyhmaWxlID0gImRhdGEvZXhwcmVzc2lvbi9heG9sb3RsL3BhbGxpdW1fbmV1cm9uYWxfR2x1dF9yZXMwNV9oYXJtb255LlJEUyIpCmF4b2xvdGxfbmV1cm9ucyA9IHJlYWRSRFMoZmlsZSA9ICJkYXRhL2V4cHJlc3Npb24vYXhvbG90bC9wYWxsaXVtX25ldXJvbmFsX3JlczA3X2hhcm1vbnkuUkRTIikKYXhvbG90bF9hbGwgPSByZWFkUkRTKGZpbGUgPSAiZGF0YS9leHByZXNzaW9uL2F4b2xvdGwvcGFsbGl1bXJlczA3X2hhcm1vbnkuUkRTIikKYGBgCgpNZXJnZSBheG9sb3RsIGFubm90YXRpb25zIHRvZ2V0aGVyIGludG8gYSBzaW5nbGUgb2JqZWN0CgpgYGB7cn0KZ2FiYV9jbCA9IHBhc3RlMCgiR0FCQV8iLCBheG9sb3RsX2dhYmEkc2V1cmF0X2NsdXN0ZXJzKQpuYW1lcyhnYWJhX2NsKSA9IGNvbG5hbWVzKGF4b2xvdGxfZ2FiYSkKZ2x1dF9jbCA9IHBhc3RlMCgiR2x1dF8iLCBheG9sb3RsX2dsdXQkc2V1cmF0X2NsdXN0ZXJzKQpuYW1lcyhnbHV0X2NsKSA9IGNvbG5hbWVzKGF4b2xvdGxfZ2x1dCkKb3RoZXIgPSBheG9sb3RsX2FsbCRjbGFzc2lmaWNhdGlvbl8yWyEoY29sbmFtZXMoYXhvbG90bF9hbGwpICVpbiUgYyhuYW1lcyhnbHV0X2NsKSwgbmFtZXMoZ2FiYV9jbCkpKV0KYWxsX2Fubm90ID0gZGF0YS5mcmFtZSgiYWxsX2Fubm90IiA9IGMob3RoZXIsIGdhYmFfY2wsIGdsdXRfY2wpKQpheG9sb3RsX2FsbCA9IEFkZE1ldGFEYXRhKGF4b2xvdGxfYWxsLCBtZXRhZGF0YSA9IGFsbF9hbm5vdCkKYXhvbG90bF9hbGxfZmlsdCA9IGF4b2xvdGxfYWxsWywhaXMubmEoYXhvbG90bF9hbGwkYWxsX2Fubm90KSAmICFncmVwbCgibmV1cm9uIiwgYXhvbG90bF9hbGwkYWxsX2Fubm90KV0KYGBgCgpPcmdhbmlzZSBtZXRhZGF0YSBpbnRvIGEgbGlzdAoKYGBge3J9Cm1ldGFfbGlzdCA9IGxpc3QoImV4cF90dXIiID0gdHVydGxlX2FsbEBtZXRhLmRhdGEsICJleHBfbGl6IiA9IGxpemFyZF9hbGxAbWV0YS5kYXRhLCAKICAgICAgICAgICAgICAgICAiZXhwX2RyZSIgPSBkcmVyaW9fYnJhaW5AbWV0YS5kYXRhLCAiZXhwX3plZiIgPSBmaW5jaGVzX2xpc3QkWkZAbWV0YS5kYXRhLCAKICAgICAgICAgICAgICAgICAiZXhwX2JlZiIgPSBmaW5jaGVzX2xpc3QkQkZAbWV0YS5kYXRhLCAiZXhwX21vdSIgPSBtb3VzZV9icmFpbkBtZXRhLmRhdGEsIAogICAgICAgICAgICAgICAgICJleHBfYXhvIiA9IGF4b2xvdGxfYWxsX2ZpbHRAbWV0YS5kYXRhKQpjbF9pZF9saXN0ID0gbGlzdCgiY2x1c3RlciIsICJjbHVzdGVycyIsICJNb2QyLjUiLCAic2V1cmF0X2NsdXN0ZXJzIiwgInNldXJhdF9jbHVzdGVycyIsICJUYXhvbm9teV9ncm91cCIsCiAgICAgICAgICAgICAgICAgICJhbGxfYW5ub3QiKQpuYW1lcyhjbF9pZF9saXN0KSA9IG5hbWVzKG1ldGFfbGlzdCkKYGBgCgpMb2FkIG5lY2Vzc2FyeSBlZ2dOT0cgZGF0YQoKYGBge3J9CmFubm90X2xpc3QgPSByZWFkUkRTKGZpbGUgPSAiZGF0YS9lZ2dOT0cvYW5ub3RhdGlvbl9saXN0LlJEUyIpCmBgYAoKCgojIFByZXBhcmUgZGF0YXNldHMKUmVmb3JtYXQgbWF0cmljZXMgdG8gaGF2ZSBvbmx5IG9uZTJvbmUgb3J0aG9sb2dzIHRoYXQgYXJlIHNoYXJlZAoKYGBge3J9CmNvdW50c19saXN0ID0gbGlzdChleHBfdHVyID0gdHVydGxlX2FsbEBhc3NheXMkUk5BQGNvdW50cywgZXhwX2xpeiA9IGxpemFyZF9hbGxAYXNzYXlzJFJOQUBjb3VudHMsCiAgICAgICAgICAgICAgICAgICBleHBfZHJlID0gZHJlcmlvX2JyYWluQGFzc2F5cyRSTkFAY291bnRzLCBleHBfemVmID0gZmluY2hlc19saXN0JFpGQGFzc2F5cyRSTkFAY291bnRzLAogICAgICAgICAgICAgICAgICAgZXhwX2JlZiA9IGZpbmNoZXNfbGlzdCRCRkBhc3NheXMkUk5BQGNvdW50cywgZXhwX21vdSA9IG1vdXNlX2JyYWluQGFzc2F5cyRSTkFAY291bnRzLAogICAgICAgICAgICAgICAgICAgZXhwX2F4byA9IGF4b2xvdGxfYWxsX2ZpbHRAYXNzYXlzJFJOQUBjb3VudHMpCgpyZWZvcm1hdFdpdGhPcnRoID0gZnVuY3Rpb24oZWdnbm9nX2Fubm90LCBjb3VudHNfbCwgc3BfbGlzdCA9IGMoIkNwaWN0YSIsICJQdml0dGljZXBzIikpewogIGxfZ19zcCA9IGxpc3QoKQogIGZvcihzcGkgaW4gMTpsZW5ndGgoc3BfbGlzdCkpewogICAgY3AgPSBuYW1lcyhjb3VudHNfbClbc3BpXQogICAgc3AgPSBzcF9saXN0W3NwaV0KICAgIGxfZ19zcFtbY3BdXSA9IGVnZ25vZ19hbm5vdFtbc3BdXVssYygyLDUpXQogICAgbF9nX3NwW1tjcF1dID0gbF9nX3NwW1tjcF1dW2xfZ19zcFtbY3BdXSRQcmVmZXJyZWRfbmFtZSE9Ii0iICYgIWlzLm5hKGxfZ19zcFtbY3BdXSRQcmVmZXJyZWRfbmFtZSksXQogICAgbF9nX3NwW1tjcF1dJFByZWZlcnJlZF9uYW1lID0gdG91cHBlcihsX2dfc3BbW2NwXV0kUHJlZmVycmVkX25hbWUpCiAgICBsX2dfc3BbW2NwXV0gPSB1bmlxdWUobF9nX3NwW1tjcF1dKQogICAgbF9nX3NwW1tjcF1dID0gbF9nX3NwW1tjcF1dWyEobF9nX3NwW1tjcF1dJGdlbmUgJWluJSBsX2dfc3BbW3NwXV0kZ2VuZVtkdXBsaWNhdGVkKGxfZ19zcFtbY3BdXSRnZW5lKV0pLF0KICAgIGxfZ19zcFtbY3BdXSA9IGxfZ19zcFtbY3BdXVshKGxfZ19zcFtbY3BdXSRQcmVmZXJyZWRfbmFtZSAlaW4lIGxfZ19zcFtbY3BdXSRQcmVmZXJyZWRfbmFtZVtkdXBsaWNhdGVkKGxfZ19zcFtbY3BdXSRQcmVmZXJyZWRfbmFtZSldKSxdCiAgfQogIAogIGFsbF9zcF9tYXRjaCA9IGxfZ19zcCAlPiUgcmVkdWNlKGZ1bGxfam9pbiwgYnkgPSAiUHJlZmVycmVkX25hbWUiKQogIGFsbF9zcF9tYXRjaCA9IGFsbF9zcF9tYXRjaFssYygyLDEsMzpuY29sKGFsbF9zcF9tYXRjaCkpXQogIGNvbG5hbWVzKGFsbF9zcF9tYXRjaCkgPSBjKCJQcmVmZXJyZWRfbmFtZSIsIG5hbWVzKGNvdW50c19sKSkKICAKICAjIG9ubHkga2VlcCBnZW5lcyBhcHBlYXJpbmcgaW4gZXhwIG1hdHJpeAogIGdfaW4gPSBsYXBwbHkoY29sbmFtZXMoYWxsX3NwX21hdGNoKVstMV0sIAogICAgICAgICAgICAgICAgZnVuY3Rpb24oeCkgdG91cHBlcihhbGxfc3BfbWF0Y2hbLHhdKSAlaW4lIHRvdXBwZXIocm93bmFtZXMoY291bnRzX2xbW3hdXSkpKQogIGtlZXAgPSByZXAoVCwgbnJvdyhhbGxfc3BfbWF0Y2gpKQogIGZvcihuIGluIDE6bGVuZ3RoKGdfaW4pKXsKICAgIGtlZXAgPSBrZWVwICYgZ19pbltbbl1dCiAgfQogIGFsbF9zcF9tYXRjaF9maWx0ID0gYWxsX3NwX21hdGNoW2tlZXAsXQogIAogICMgcmVtb3ZlIGR1cGxpY2F0ZWQgLSBvbmx5IHdhbnQgb25lMm9uZQogIGdfZHUgPSBsYXBwbHkoY29sbmFtZXMoYWxsX3NwX21hdGNoX2ZpbHQpWy0xXSwgCiAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSAhKGFsbF9zcF9tYXRjaF9maWx0Wyx4XSAlaW4lIGFsbF9zcF9tYXRjaF9maWx0Wyx4XVtkdXBsaWNhdGVkKGFsbF9zcF9tYXRjaF9maWx0Wyx4XSldKSkKICBrZWVwID0gcmVwKFQsIG5yb3coYWxsX3NwX21hdGNoX2ZpbHQpKQogIGZvcihuIGluIDE6bGVuZ3RoKGdfZHUpKXsKICAgIGtlZXAgPSBrZWVwICYgZ19kdVtbbl1dCiAgfQogIGFsbF9zcF9tYXRjaF9maWx0ID0gYWxsX3NwX21hdGNoX2ZpbHRba2VlcCxdCiAgCiAgIyBwdXR0aW5nIGV2ZXJ5dGhpbmcgaW50byB1cHBlcmNhc2UgYmVjYXVzZSB0aGUgZHJlcmlvIGRhdGEgaXMgaW4gdXBwZXJjYXNlIGZvciBzb21lIHJlYXNvbi4uLgogIGZvcihuIGluIGNvbG5hbWVzKGFsbF9zcF9tYXRjaF9maWx0KSl7YWxsX3NwX21hdGNoX2ZpbHRbLG5dID0gdG91cHBlcihhbGxfc3BfbWF0Y2hfZmlsdFssbl0pfQogIAogIHJldHVybihhbGxfc3BfbWF0Y2hfZmlsdCkKfQoKZ2VuZV9tYXRjaCA9IHJlZm9ybWF0V2l0aE9ydGgoYW5ub3RfbGlzdCwgY291bnRzX2xpc3QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcF9saXN0ID0gYygiQ3BpY3RhIiwgIlB2aXR0aWNlcHMiLCAiRHJlcmlvIiwgIlRndXR0YXRhIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJUZ3V0dGF0YSIsICJNbXVzY3VsdXMiLCAiQW1leGljYW51bSIpKQpnZW5lX21hdGNoX2NvbXAgPSBnZW5lX21hdGNoW2NvbXBsZXRlLmNhc2VzKGdlbmVfbWF0Y2gpLF0KCmxhcHBseShjb3VudHNfbGlzdCwgZGltKQpmb3IobiBpbiBuYW1lcyhjb3VudHNfbGlzdCkpewogIGNvdW50c19saXN0W1tuXV0gPSBjb3VudHNfbGlzdFtbbl1dW3RvdXBwZXIocm93bmFtZXMoY291bnRzX2xpc3RbW25dXSkpICVpbiUgZ2VuZV9tYXRjaF9jb21wWyxuXSxdCiAgcm93bmFtZXMoY291bnRzX2xpc3RbW25dXSkgPSBnZW5lX21hdGNoX2NvbXAkUHJlZmVycmVkX25hbWVbbWF0Y2godG91cHBlcihyb3duYW1lcyhjb3VudHNfbGlzdFtbbl1dKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9tYXRjaF9jb21wWyxuXSldCn0KbGFwcGx5KGNvdW50c19saXN0LCBkaW0pCgojIG1ha2Ugc3VyZSB0aGV5IGhhdmUgdGhlIHNhbWUgb3JkZXJpbmcKY291bnRzX2xpc3QgPSBsYXBwbHkoY291bnRzX2xpc3QsIGZ1bmN0aW9uKHgpIHhbcm93bmFtZXMoY291bnRzX2xpc3QkZXhwX2F4byksXSkKYGBgCgpSZW5vcm1hbGlzZSwgZ2V0IG1lYW4gZXhwcmVzc2lvbiBhbmQgbWFya2VycwoKYGBge3J9CnNldXJhdF9vbmUyb25lID0gbGlzdCgpCmF2Z19leHAgPSBsaXN0KCkKbWtfbGlzdCA9IGxpc3QoKQpmb3IobiBpbiBuYW1lcyhjb3VudHNfbGlzdCkpewogIHByaW50KG4pCiAgc2V1cmF0X29uZTJvbmVbW25dXSA9IENyZWF0ZVNldXJhdE9iamVjdChjb3VudHNfbGlzdFtbbl1dLCBtZXRhLmRhdGEgPSBtZXRhX2xpc3RbW25dXSkKICBJZGVudHMoc2V1cmF0X29uZTJvbmVbW25dXSkgPSBzZXVyYXRfb25lMm9uZVtbbl1dQG1ldGEuZGF0YVssY2xfaWRfbGlzdFtbbl1dXQogIHNldXJhdF9vbmUyb25lW1tuXV0gPSBzdXBwcmVzc1dhcm5pbmdzKFNDVHJhbnNmb3JtKHNldXJhdF9vbmUyb25lW1tuXV0sIGRvLmNvcnJlY3QudW1pID0gVCwgdmVyYm9zZSA9IEYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQudXNlID0gMSwgdmFycy50by5yZWdyZXNzID0gIm5Db3VudF9STkEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcmlhYmxlLmZlYXR1cmVzLnJ2LnRoID0gMSwgcmV0dXJuLm9ubHkudmFyLmdlbmVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJpYWJsZS5mZWF0dXJlcy5uID0gTlVMTCkpCiAgYXZnX2V4cFtbbl1dID0gQXZlcmFnZUV4cHJlc3Npb24oc2V1cmF0X29uZTJvbmVbW25dXSwgYXNzYXlzID0gIlNDVCIpJFNDVAogIG1rX2xpc3RbW25dXSA9IEZpbmRBbGxNYXJrZXJzKHNldXJhdF9vbmUyb25lW1tuXV0sIGxvZ2ZjLnRocmVzaG9sZCA9IDAuMiwgb25seS5wb3MgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBzZXVkb2NvdW50LnVzZSA9IDAuMSwgYXNzYXkgPSAiU0NUIikKfQpzYXZlKHNldXJhdF9vbmUyb25lLCBhdmdfZXhwLCBta19saXN0LCBmaWxlID0gInJlc3VsdHMvY3Jvc3Nfc3BlY2llc19jb3JyZWxhdGlvbi9vbmUyb25lX3NldXJhdF9hdmdfbWsuUkRhdGEiKQpgYGAKCgoKIyBDb3JyZWxhdGlvbiBhbmFseXNpcwpMb2FkIGRhdGEKCmBgYHtyfQpsb2FkKCJyZXN1bHRzL2Nyb3NzX3NwZWNpZXNfY29ycmVsYXRpb24vb25lMm9uZV9zZXVyYXRfYXZnX21rLlJEYXRhIikKYGBgCgpTZWxlY3QgZ2VuZXMgdG8gdXNlIGZvciBjb3JyZWxhdGlvbnMsIG5vcm1hbGlzZSBkYXRhCgpgYGB7cn0KIyBnZXQgdG9wIG1hcmtlcnMKdG9wX21rX2xpc3QgPSBsYXBwbHkobWtfbGlzdCwgZnVuY3Rpb24oeCkgeFt4JHBfdmFsX2Fkajw9MC4wNSAmIHgkYXZnX2xvZzJGQz4wLjIsXSkKZm9yKG4gaW4gbmFtZXModG9wX21rX2xpc3QpKXsKICB0b3BfbWtfbGlzdFtbbl1dID0gdW5pcXVlKHVubGlzdCgodG9wX21rX2xpc3RbW25dXSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXJyYW5nZShkZXNjKGF2Z19sb2cyRkMpKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkoY2x1c3RlcikgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNsaWNlKDE6NTAwMDAwKSlbLCJnZW5lIl0pKQp9Cgpjb21tb25fZ2VuZXMgPSBSZWR1Y2UoaW50ZXJzZWN0LCB0b3BfbWtfbGlzdCkKY29tbW9uX2dlbmVzID0gY29tbW9uX2dlbmVzW2NvbW1vbl9nZW5lcyAlaW4lIFJlZHVjZShpbnRlcnNlY3QsIGxhcHBseShhdmdfZXhwLCByb3duYW1lcykpXQphdmdfZXhwX3N1YiA9IGxhcHBseShhdmdfZXhwLCBmdW5jdGlvbih4KSB0KGFwcGx5KHhbY29tbW9uX2dlbmVzLF0sIDEsIGZ1bmN0aW9uKHkpIHkvbWVhbih5KSkpKQpsYXBwbHkoYXZnX2V4cF9zdWIsIGRpbSkKYGBgCgoKCmBgYHtyfQp1cHNldChmcm9tTGlzdCh0b3BfbWtfbGlzdFtjKDYsNCw1LDIsMSw3LDMpXSksIHNldHMgPSBuYW1lcyh0b3BfbWtfbGlzdClbYyg2LDQsNSwyLDEsNywzKV0sIAogICAgICBvcmRlci5ieSA9ICJmcmVxIiwga2VlcC5vcmRlciA9IFRSVUUsIG51bWJlci5hbmdsZXMgPSAwLCBuaW50ZXJzZWN0cyA9IDMwLCBwb2ludC5zaXplID0gMS44KQpgYGAKCkNhbGN1bGF0ZSBjb3JyZWxhdGlvbnMKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CnNwX2Nvcl9sID0gbGlzdCgpCmZvcihpIGluIG5hbWVzKGF2Z19leHBfc3ViKSl7CiAgZm9yKGogaW4gbmFtZXMoYXZnX2V4cF9zdWIpKXsKICAgIHByaW50KHBhc3RlKGksIGopKQogICAgc3BfY29yX2xbW3Bhc3RlMChpLCAiLiIsIGopXV0gPSBjb3IoYXZnX2V4cF9zdWJbW2ldXSwgYXZnX2V4cF9zdWJbW2pdXSwgbWV0aG9kID0gInNwIikKICB9Cn0KYGBgCgpQbG90IGNvcnJlbGF0aW9uIG1hdHJpY2VzCgpgYGB7cn0KY29scyA9IGNvbG9yUmFtcFBhbGV0dGUoYyhyZXYoUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDksICJCbHVlcyIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoOSwgIlJlZHMiKSkpKDEwMCkKZm9yKG4gaW4gbmFtZXMoc3BfY29yX2wpKXsKICBwbHRtYXQgPSB0KHNwX2Nvcl9sW1tuXV0pCiAgYnIgPSBjKHNlcShtaW4ocGx0bWF0KSwgMCwgbGVuZ3RoLm91dCA9IDUxKSwgc2VxKDAsIG1heChwbHRtYXQpLCBsZW5ndGgub3V0ID0gNTEpWy0xXSkKICBwaGVhdG1hcDo6cGhlYXRtYXAocGx0bWF0LCBicmVha3MgPSBiciwgY29sb3IgPSBjb2xzLAogICAgICAgICAgICAgICAgICAgICBjbHVzdGVyaW5nX21ldGhvZCA9ICJ3YXJkLkQyIiwgbWFpbiA9IG4pCn0KYGBgCgpVTUFQIGZyb20gbWVhbiBleHByZXNzaW9uCgpgYGB7cn0KZm9yKG4gaW4gbmFtZXMoYXZnX2V4cF9zdWIpKXsKICBzcCA9IHN0cnNwbGl0KG4sICJfIilbWzFdXVsyXQogIGNvbG5hbWVzKGF2Z19leHBfc3ViW1tuXV0pID0gcGFzdGUwKHNwLCAiLi4iLCBjb2xuYW1lcyhhdmdfZXhwX3N1Yltbbl1dKSkKfQphdmdfZXhwX2FsbCA9IHQoUmVkdWNlKGNiaW5kLCBhdmdfZXhwX3N1YikpCnNldC5zZWVkKDI5NTQpCmwgPSB1d290Ojp1bWFwKGF2Z19leHBfYWxsLCBtZXRyaWMgPSAiY29zaW5lIiwgcmV0X25uID0gVCwgbl9lcG9jaHMgPSAxMDAwKQpsX3NwID0gZGF0YS5mcmFtZShsJGVtYmVkZGluZykKcm93bmFtZXMobF9zcCkgPSByb3duYW1lcyhhdmdfZXhwX2FsbCkKbF9zcCRjbCA9IHJvd25hbWVzKGF2Z19leHBfYWxsKQpsX3NwJHNwID0gdW5saXN0KGxhcHBseShzdHJzcGxpdChsX3NwJGNsLCAiLi4iLCBmaXhlZCA9IFQpLCBmdW5jdGlvbih4KSB4WzFdKSkKbF9zcCRjbCA9IHVubGlzdChsYXBwbHkoc3Ryc3BsaXQobF9zcCRjbCwgIi4uIiwgZml4ZWQgPSBUKSwgZnVuY3Rpb24oeCkgeFsyXSkpCgpkZl9uYW1lcyA9IGxfc3BbbF9zcCRzcD09ImF4byIgfCAobF9zcCRYMjwoLTQpICYgbF9zcCRzcD09Im1vdSIpIHwgKGxfc3AkWDI+MC41ICYgbF9zcCRzcD09Im1vdSIpLF0KZ2dwbG90KGxfc3AsIGFlcyh4ID0gWDEsIHkgPSBYMiwgY29sb3VyID0gc3ApKSsKICBnZW9tX3BvaW50KCkrCiAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKGRhdGEgPSBkZl9uYW1lcywgbWFwcGluZyA9IGFlcyh4ID0gWDEsIHkgPSBYMiwgbGFiZWwgPSBjbCksIG1heC5vdmVybGFwcyA9IDEwMCkrCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKTmV0d29yayBpbiBNRFMgZnJvbSBjb3JyZWxhdGlvbnMKCmBgYHtyfQpjb3JfdGFiX2wgPSBsaXN0KCkKZm9yKG4gaW4gbmFtZXMoc3BfY29yX2wpKXsKICBkYXQgPSByZXNoYXBlMjo6bWVsdChzcF9jb3JfbFtbbl1dKQogIGRhdCRzcDEgPSBzdHJzcGxpdChzdHJzcGxpdChuLCAiLiIsIGZpeGVkID0gVClbWzFdXVsxXSwgIl8iLCBmaXhlZCA9IFQpW1sxXV1bMl0KICBkYXQkc3AyID0gc3Ryc3BsaXQoc3Ryc3BsaXQobiwgIi4iLCBmaXhlZCA9IFQpW1sxXV1bMl0sICJfIiwgZml4ZWQgPSBUKVtbMV1dWzJdCiAgCiAgZGF0JFZhcjEgPSBwYXN0ZTAoZGF0JHNwMSwgIl8iLCBkYXQkVmFyMSkKICBkYXQkVmFyMiA9IHBhc3RlMChkYXQkc3AyLCAiXyIsIGRhdCRWYXIyKQogIGNvcl90YWJfbFtbbl1dID0gZGF0Cn0KY29yX3RhYiA9IFJlZHVjZShyYmluZCwgY29yX3RhYl9sKQpjb3Jfc3AgPSByZXNoYXBlMjo6ZGNhc3QoY29yX3RhYiwgVmFyMSB+IFZhcjIsIHZhbHVlLnZhciA9ICJ2YWx1ZSIpCnJvd25hbWVzKGNvcl9zcCkgPSBjb3Jfc3BbLDFdCmNvcl9zcCA9IGNvcl9zcFssLTFdCmFkal9tYXQgPSBjb3Jfc3A+PTAuMwoKIyBidWlsZCBncmFwaCwgcHJvamVjdCB3aXRoIE1EUwpuZXR3b3JrX3NwID0gZ3JhcGhfZnJvbV9hZGphY2VuY3lfbWF0cml4KGFkal9tYXQsIHdlaWdodGVkPVQsIG1vZGU9InVuZGlyZWN0ZWQiLCBkaWFnPUYpCmxfc3AgPSBpZ3JhcGg6OmxheW91dF93aXRoX21kcyhuZXR3b3JrX3NwKQpsX3NwID0gZGF0YS5mcmFtZShsX3NwKQpsX3NwJGNsID0gY29sbmFtZXMoYWRqX21hdCkKcm93bmFtZXMobF9zcCkgPSBjb2xuYW1lcyhhZGpfbWF0KQpsX3NwJHNwID0gdW5saXN0KGxhcHBseShzdHJzcGxpdChsX3NwJGNsLCAiXyIpLCBmdW5jdGlvbih4KSB4WzFdKSkKbF9zcCRjbCA9IHVubGlzdChsYXBwbHkoc3Ryc3BsaXQobF9zcCRjbCwgIl8iKSwgZnVuY3Rpb24oeCkgeFsyXSkpCgpkZl9uYW1lcyA9IGxfc3BbbF9zcCRzcD09ImF4byIsXQpnZ3Bsb3QobF9zcCwgYWVzKHggPSBYMSwgeSA9IFgyLCBjb2xvdXIgPSBzcCkpKwogIGdlb21fcG9pbnQoKSsKICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoZGF0YSA9IGRmX25hbWVzLCBtYXBwaW5nID0gYWVzKHggPSBYMSwgeSA9IFgyLCBsYWJlbCA9IGNsKSwgbWF4Lm92ZXJsYXBzID0gMTAwKSsKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgoKCmBgYHtyfQpjb21tb25fZ2VuZXMgPSBSZWR1Y2UoaW50ZXJzZWN0LCB0b3BfbWtfbGlzdCkKYW55X2dlbmVzID0gUmVkdWNlKHVuaW9uLCB0b3BfbWtfbGlzdCkKbm9uX3NoYXJlZF9nZW5lcyA9IGFueV9nZW5lc1shKGFueV9nZW5lcyAlaW4lIGNvbW1vbl9nZW5lcyldCm5vbl9zaGFyZWRfZ2VuZXMgPSBub25fc2hhcmVkX2dlbmVzW25vbl9zaGFyZWRfZ2VuZXMgJWluJSBSZWR1Y2UoaW50ZXJzZWN0LCBsYXBwbHkoYXZnX2V4cCwgcm93bmFtZXMpKV0KYXZnX2V4cF9zdWIgPSBsYXBwbHkoYXZnX2V4cCwgZnVuY3Rpb24oeCkgdChhcHBseSh4W25vbl9zaGFyZWRfZ2VuZXMsXSwgMSwgZnVuY3Rpb24oeSkgeS9tZWFuKHkpKSkpCmxhcHBseShhdmdfZXhwX3N1YiwgZGltKQoKZm9yKG4gaW4gbmFtZXMoYXZnX2V4cF9zdWIpKXsKICBzcCA9IHN0cnNwbGl0KG4sICJfIilbWzFdXVsyXQogIGNvbG5hbWVzKGF2Z19leHBfc3ViW1tuXV0pID0gcGFzdGUwKHNwLCAiLi4iLCBjb2xuYW1lcyhhdmdfZXhwX3N1Yltbbl1dKSkKfQphdmdfZXhwX2FsbCA9IHQoUmVkdWNlKGNiaW5kLCBhdmdfZXhwX3N1YikpCnNldC5zZWVkKDI5NTQpCmwgPSB1d290Ojp1bWFwKGF2Z19leHBfYWxsLCBtZXRyaWMgPSAiY29zaW5lIiwgcmV0X25uID0gVCwgbl9lcG9jaHMgPSAxMDAwKQpsX3NwID0gZGF0YS5mcmFtZShsJGVtYmVkZGluZykKcm93bmFtZXMobF9zcCkgPSByb3duYW1lcyhhdmdfZXhwX2FsbCkKbF9zcCRjbCA9IHJvd25hbWVzKGF2Z19leHBfYWxsKQpsX3NwJHNwID0gdW5saXN0KGxhcHBseShzdHJzcGxpdChsX3NwJGNsLCAiLi4iLCBmaXhlZCA9IFQpLCBmdW5jdGlvbih4KSB4WzFdKSkKbF9zcCRjbCA9IHVubGlzdChsYXBwbHkoc3Ryc3BsaXQobF9zcCRjbCwgIi4uIiwgZml4ZWQgPSBUKSwgZnVuY3Rpb24oeCkgeFsyXSkpCgpkZl9uYW1lcyA9IGxfc3BbbF9zcCRzcD09ImF4byIsXQpnZ3Bsb3QobF9zcCwgYWVzKHggPSBYMSwgeSA9IFgyLCBjb2xvdXIgPSBzcCkpKwogIGdlb21fcG9pbnQoKSsKICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoZGF0YSA9IGRmX25hbWVzLCBtYXBwaW5nID0gYWVzKHggPSBYMSwgeSA9IFgyLCBsYWJlbCA9IGNsKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heC5vdmVybGFwcyA9IDEwMCwgc2l6ZSA9IDMpKwogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCg==